package com.jgp.mico.client;

import com.jgp.common.utils.JGPUtil;
import com.jgp.sys.props.MicroConfigPropBean;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;

/**
 * 项目   jgp-micro-provider
 * 作者   loufei
 * 时间   2018/11/30
 */
public class MicroClient extends RestTemplate {
    
    private static final String POST = "post";
    private static final String GET = "get";
    private String profile;
    private MicroConfigPropBean microConfigPropBean;
    
    public MicroClient(String profile, MicroConfigPropBean microConfigPropBean) {
        this.profile = profile;
        this.microConfigPropBean = microConfigPropBean;
    }
    
    /**
     * @param appName 应用名称
     * @param api     接口
     * @param clazz   实体类型
     * @param <T>     实体类型
     * @return 实体
     */
    public <T> T get(String appName, String api, Class<T> clazz) {
        return get(appName, api, clazz, null, null);
    }
    
    /**
     * @param appName          应用名称
     * @param api              接口
     * @param clazz            实体类型
     * @param uriVariableStyle 路径变量风格 {@link UriVariableStyle} UriVariableStyle.PATH_STYLE路径风格
     *                         (/api/user/1/2)<br>
     *                         UriVariableStyle.KEY_VALUE_STYLE 键值对风格(/api/user?p=1&n=2)<br>
     * @param uriVariables     变量 {@link LinkedHashMap}
     * @param <T>              实体类型
     * @return 实体
     */
    public <T> T get(String appName, String api, Class<T> clazz, UriVariableStyle uriVariableStyle,
                     LinkedHashMap<String, Object> uriVariables) {
        String json = remoteInvoke(GET, appName, api, String.class, uriVariableStyle, uriVariables, false,null);
        return JGPUtil.toBean(json, clazz);
    }
    
    /**
     * @param appName 应用名称
     * @param api     接口
     * @param clazz   实体类型
     * @param <T>     实体类型
     * @return 实体列表
     */
    public <T> List<T> getList(String appName, String api, Class<T> clazz) throws IOException {
        return getList(appName, api, clazz, null, null);
    }
    
    /**
     * @param appName          应用名称
     * @param api              接口
     * @param clazz            实体类型
     * @param uriVariableStyle 路径变量风格 {@link UriVariableStyle} UriVariableStyle.PATH_STYLE路径风格
     *                         (/api/user/1/2)<br>
     *                         UriVariableStyle.KEY_VALUE_STYLE 键值对风格(/api/user?p=1&n=2)<br>
     * @param uriVariables     变量 {@link LinkedHashMap}
     * @param <T>              实体类型
     * @return 实体列表
     */
    public <T> List<T> getList(String appName, String api, Class<T> clazz, UriVariableStyle
            uriVariableStyle, LinkedHashMap<String, Object> uriVariables) throws IOException {
        String json = remoteInvoke(GET, appName, api, String.class, uriVariableStyle, uriVariables, false,null);
        return JGPUtil.jsonToList(json, clazz);
    }
    
    
    /**
     * @param appName 应用名称
     * @param api     接口
     * @param clazz   实体类型
     * @param params  post参数<br>
     *                可为Map和任意实体类型,如果为其他实体类型,接口方接收参数需要用 {@link com.jgp.common.annotation.UIParam}
     *                来获取参数<br>
     * @param <T>     实体类型
     * @return 实体
     */
    public <T> T post(String appName, String api, Class<T> clazz, Object... params) {
        return post(appName, api, clazz, null, null, params);
    }
    
    /**
     * @param appName          应用名称
     * @param api              接口
     * @param clazz            实体类型
     * @param uriVariableStyle 路径变量风格 {@link UriVariableStyle} UriVariableStyle.PATH_STYLE路径风格
     *                         (/api/user/1/2)<br>
     *                         UriVariableStyle.KEY_VALUE_STYLE 键值对风格(/api/user?p=1&n=2)<br>
     * @param uriVariables     变量 {@link LinkedHashMap}
     * @param params           post参数<br>
     *                         可为Map和任意实体类型,如果为其他实体类型,接口方接收参数需要用 {@link com.jgp.common.annotation.UIParam}
     *                         来获取参数<br>
     * @param <T>              实体类型
     * @return 实体
     */
    public <T> T post(String appName, String api, Class<T> clazz, UriVariableStyle uriVariableStyle,
                      LinkedHashMap<String, Object> uriVariables, Object... params) {
        if(!clazz.getSimpleName().equals("Result")){
            return remoteInvoke(POST, appName, api, clazz, uriVariableStyle, uriVariables,false, params);
        }else{
            String json = remoteInvoke(POST, appName, api, String.class, uriVariableStyle, uriVariables,false, params);
            return JGPUtil.toBean(json,clazz);
        }
    }
    
    public <T> T postJson(String appName, String api, Class<T> clazz, Object params) {
        if(!clazz.getSimpleName().equals("Result")){
            return remoteInvoke(POST, appName, api, clazz, null, null,true, params);
        }else{
            String json = remoteInvoke(POST, appName, api, String.class, null, null,true, params);
            return JGPUtil.toBean(json,clazz);
        }
    }
    
    /**
     * @param appName 应用名称
     * @param api     接口
     * @param clazz   实体类型
     * @param params  post参数<br>
     *                可为Map和任意实体类型,如果为其他实体类型,接口方接收参数需要用 {@link com.jgp.common.annotation.UIParam}
     *                来获取参数<br>
     * @param <T>     实体类型
     * @return 实体列表
     */
    public <T> List<T> postList(String appName, String api, Class<T> clazz, Object... params) throws
            IOException {
        return postList(appName, api, clazz, null, null, params);
    }
    
    /**
     * @param appName          应用名称
     * @param api              接口
     * @param clazz            实体类型
     * @param uriVariableStyle 路径变量风格 {@link UriVariableStyle} UriVariableStyle.PATH_STYLE路径风格
     *                         (/api/user/1/2)<br>
     *                         UriVariableStyle.KEY_VALUE_STYLE 键值对风格(/api/user?p=1&n=2)<br>
     * @param uriVariables     变量 {@link LinkedHashMap}
     * @param params           post参数<br>
     *                         可为Map和任意实体类型,如果为其他实体类型,接口方接收参数需要用 {@link com.jgp.common.annotation.UIParam}
     *                         来获取参数<br>
     * @param <T>              实体类型
     * @return 实体列表
     */
    public <T> List<T> postList(String appName, String api, Class<T> clazz, UriVariableStyle
            uriVariableStyle, LinkedHashMap<String, Object> uriVariables, Object... params) throws
            IOException {
        String json = remoteInvoke(POST, appName, api, String.class, uriVariableStyle, uriVariables,false, params);
        return JGPUtil.jsonToList(json, clazz);
    }
    
    private <T> T remoteInvoke(String method, String appName, String api, Class<T> clazz, UriVariableStyle
            uriVariableStyle, LinkedHashMap<String, Object> uriVariables,boolean json, Object... params) {
        if(Objects.nonNull(this.profile)&&!"".equals(this.profile)&&!"prod".equals(this.profile)){
            MicroConfigPropBean.Test test = this.microConfigPropBean.getTest();
            if(Objects.nonNull(test)&&test.getEnabled()){
                Map<String,String> relTarget = test.getRelTarget();
                String profile = test.getProfile();
                if(Objects.nonNull(relTarget)&&Objects.nonNull(relTarget.get(appName))){
                    profile = relTarget.get(appName);
                }
                appName = appName+"-"+profile;
            }else{
                appName = appName+"-"+this.profile.toLowerCase();
            }
        }
        
        String url = (microConfigPropBean.getSsl() ? "https://" : "http://") + appName + api;
        HttpHeaders headers = new HttpHeaders();
        headers.add("__JGP_MICRO_CLIENT__","yes");
        List<String> cookie = getCookie();
        if(!CollectionUtils.isEmpty(cookie)){
            headers.put("Cookie",cookie);
        }
    
        if (Objects.nonNull(uriVariables) && uriVariables.size() > 0) {
            url = appendUrl(method,url, uriVariableStyle, uriVariables);
        } else {
            uriVariables = new LinkedHashMap<>();
        }
        
        if(json){
            headers.setContentType(MediaType.APPLICATION_JSON);
            //RestTemplate带参传的时候要用HttpEntity<?>对象传递
            HttpEntity<String> request = new HttpEntity<>(JGPUtil.toJson(params[0]), headers);
            return super.postForObject(url, request, clazz);
        }else{
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    
            if (POST.equals(method)) {
                MultiValueMap<String, Object> query = new LinkedMultiValueMap<>();
                Map<String, Object> requestMap = new HashMap<>();
                if(Objects.nonNull(params)){
                    if(params.length > 1){
                        for (Object param : params) {
                            if (param instanceof Map) {
                                requestMap.putAll(JGPUtil.castToRequestMap(param));
                            } else {
                                if(Objects.isNull(param)) {
                                    logger.error(param.getClass()+"不能为空",new NullPointerException());
                                    continue;
                                }
                                requestMap.putAll(JGPUtil.castToRequestMapMultiObject(param));
                            }
                        }
                    }else if(params.length == 1){
                        Object param = params[0];
                        /*if (param instanceof Map) {
                            requestMap.putAll(JGPUtil.castToRequestMap(param));
                        } else {
                            if(Objects.isNull(param)) {
                                logger.error(param.getClass()+"不能为空",new NullPointerException());
                            }
                            requestMap.putAll(JGPUtil.castToRequestMapMultiObject(param));
                        }*/
                        requestMap.putAll(JGPUtil.castToRequestMap(param));
                    }
                    if (uriVariables.size() > 0) {
                        requestMap.putAll(JGPUtil.castToRequestMap(uriVariables));
                    }
                    List<String> urlParams = JGPUtil.regexExtract(url,"\\{([^}]*)\\}");
                    LinkedHashMap uvs = new LinkedHashMap();
                    LinkedHashMap<String, Object> finalUriVariables = uriVariables;
                    if(Objects.nonNull(urlParams)&&urlParams.size()>0){
                        urlParams.forEach(name -> {
                            name = name.replace("{","").replace("}","");
                            uvs.put(name, finalUriVariables.get(name));
                        });
                        uriVariables = uvs;
                    }else{
                        uriVariables = new LinkedHashMap<>();
                    }
                }
        
                query.setAll(requestMap);
                HttpEntity httpEntity = new HttpEntity(query, headers);
                return super.postForObject(url, httpEntity, clazz, uriVariables);
            } else if (GET.equals(method)) {
                return super.getForObject(url, clazz, uriVariables);
            }
    
            return null;
        }
    }
    
    public String appendUrl(String method,String url, UriVariableStyle uriVariableStyle, LinkedHashMap<String, Object>
            uriVariables) {
        if (url.endsWith("/")||url.endsWith("?")||url.endsWith("&")) {
            url = url.substring(0, url.length()-1);
        }
        if (UriVariableStyle.PATH_STYLE.equals(uriVariableStyle)) {
            StringBuffer sb = new StringBuffer(url);
            sb.append("/");
            uriVariables.forEach((name, value) -> {
                sb.append("{").append(name).append("}/");
            });
            url = sb.toString();
        }
        if(GET.equals(method)&&UriVariableStyle.KEY_VALUE_STYLE.equals(uriVariableStyle)){
            StringBuffer sb = new StringBuffer(url);
            if(sb.indexOf("?")!=-1){
                sb.append("&");
            }else {
                sb.append("?");
            }
            uriVariables.forEach((name, value) -> {
                sb.append(name).append("=").append("{").append(name).append("}").append("&");
            });
            url = sb.toString();
        }
        if (url.endsWith("/")||url.endsWith("?")||url.endsWith("&")) {
            url = url.substring(0, url.length()-1);
        }
        return url;
    }
    
    
    private static List<String> getCookie() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if(Objects.isNull(requestAttributes)) return null;
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        
        List<String> cookieList = new ArrayList<>();
        
        Cookie[] cookies = request.getCookies();
        if (cookies == null || cookies.length == 0) {
            String token = request.getHeader("Authorization");
            if(StringUtils.isNotBlank(token)){
                cookieList.add(token);
            }
            return cookieList;
        }
        
        for (Cookie cookie : cookies) {
            cookieList.add(cookie.getName() + "=" + cookie.getValue());
        }
        
        return cookieList;
    }
}
